﻿// Copyright © Tamir Khason. 
// This source has no relation to the Microsoft Source License for Silverlight Controls (March 2008 Release).
// It uses only ideas, learned from MSDN and not based on Silverlight Controls source code.
// Please see http://blogs.microsoft.co.il/blogs/tamir or http://sharpsoft.net/ for details.
// All other rights reserved. 

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Globalization;

namespace Sharpsoft.Controls
{
    public abstract class DoubleRangeBase : Control
    {
        internal double _requestedMax, _requestedMin, _requestedValueHigh, _requestedValueLow;

        #region Minimum

        /// <summary> 
        /// Gets or sets the Minimum possible Value of the double object.
        /// </summary> 
        public double Minimum
        {
            get { return (double)GetValue(MinimumProperty); }
            set { SetValue(MinimumProperty, value); }
        }

        /// <summary> 
        /// Identifies the Minimum dependency property.
        /// </summary> 
        public static readonly DependencyProperty MinimumProperty =
                    DependencyProperty.Register(
                          "Minimum",
                          typeof(double),
                          typeof(DoubleRangeBase),
                          new PropertyMetadata(OnMinimumChanged));

        /// <summary>
        /// MinimumProperty property changed handler. 
        /// </summary>
        /// <param name="d">LowHighRangeBase that changed its Minimum.</param>
        /// <param name="e">DependencyPropertyChangedEventArgs.</param> 
        private static void OnMinimumChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            DoubleRangeBase _LowHighRangeBase = d as DoubleRangeBase;

            if (_LowHighRangeBase != null)
            {
                _LowHighRangeBase._requestedMin = (double)e.NewValue;
                _LowHighRangeBase.CoerceMaximum();
                _LowHighRangeBase.CoerceValues();
                _LowHighRangeBase.OnMinimumChanged((double)e.OldValue, (double)e.NewValue);
            }
        }
        #endregion

        #region Maximum

        /// <summary> 
        /// Gets or sets the Maximum possible Value of the int object.
        /// </summary> 
        public double Maximum
        {
            get { return (double)GetValue(MaximumProperty); }
            set { SetValue(MaximumProperty, value); }
        }

        /// <summary> 
        /// Identifies the Maximum dependency property.
        /// </summary> 
        public static readonly DependencyProperty MaximumProperty =
                    DependencyProperty.Register(
                          "Maximum",
                          typeof(double),
                          typeof(DoubleRangeBase),
                          new PropertyMetadata(OnMaximumChanged));

        /// <summary>
        /// MaximumProperty property changed handler. 
        /// </summary>
        /// <param name="d">LowHighRangeBase that changed its Maximum.</param>
        /// <param name="e">DependencyPropertyChangedEventArgs.</param> 
        private static void OnMaximumChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            DoubleRangeBase _LowHighRangeBase = d as DoubleRangeBase;
            if (_LowHighRangeBase != null)
            {
                _LowHighRangeBase._requestedMax = (double)e.NewValue;
                _LowHighRangeBase.CoerceMaximum();
                _LowHighRangeBase.CoerceValues();
                _LowHighRangeBase.OnMaximumChanged((double)e.OldValue, (double)e.NewValue);
            }
        }
        #endregion

        #region ValueLow

        /// <summary> 
        /// Gets or sets the ValueLow possible Value of the double object.
        /// </summary> 
        public double ValueLow
        {
            get { return (double)GetValue(ValueLowProperty); }
            set { SetValue(ValueLowProperty, value); }
        }

        /// <summary> 
        /// Identifies the ValueLow dependency property.
        /// </summary> 
        public static readonly DependencyProperty ValueLowProperty =
                    DependencyProperty.Register(
                          "ValueLow",
                          typeof(double),
                          typeof(DoubleRangeBase),
                          new PropertyMetadata(OnValueLowPropertyChanged));

        /// <summary>
        /// ValueLowProperty property changed handler. 
        /// </summary>
        /// <param name="d">LowHighRangeBase that changed its ValueLow.</param>
        /// <param name="e">DependencyPropertyChangedEventArgs.</param> 
        private static void OnValueLowPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            DoubleRangeBase _LowHighRangeBase = d as DoubleRangeBase;
            if (_LowHighRangeBase != null)
            {

                _LowHighRangeBase._requestedValueLow = (double)e.NewValue;
                _LowHighRangeBase.CoerceValues();
                _LowHighRangeBase.OnValueLowChanged((double)e.OldValue, (double)e.NewValue);
                _LowHighRangeBase.OnValueChanged((double)e.OldValue, (double)e.NewValue, ValueChangeType.Low);
            }
        }
        #endregion

        #region ValueHigh

        /// <summary> 
        /// Gets or sets the ValueHigh possible Value of the double object.
        /// </summary> 
        public double ValueHigh
        {
            get { return (double)GetValue(ValueHighProperty); }
            set { SetValue(ValueHighProperty, value); }
        }

        /// <summary> 
        /// Identifies the ValueHigh dependency property.
        /// </summary> 
        public static readonly DependencyProperty ValueHighProperty =
                    DependencyProperty.Register(
                          "ValueHigh",
                          typeof(double),
                          typeof(DoubleRangeBase),
                          new PropertyMetadata(OnValueHighPropertyChanged));

        /// <summary>
        /// ValueHighProperty property changed handler. 
        /// </summary>
        /// <param name="d">LowHighRangeBase that changed its ValueHigh.</param>
        /// <param name="e">DependencyPropertyChangedEventArgs.</param> 
        private static void OnValueHighPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            DoubleRangeBase _LowHighRangeBase = d as DoubleRangeBase;
            if (_LowHighRangeBase != null)
            {
                _LowHighRangeBase._requestedValueHigh = (double)e.NewValue;
                _LowHighRangeBase.CoerceValues();
                _LowHighRangeBase.OnValueHighChanged((double)e.OldValue, (double)e.NewValue);
                _LowHighRangeBase.OnValueChanged((double)e.OldValue, (double)e.NewValue, ValueChangeType.High);
            }
        }
        #endregion

        #region ToolTip
        /// <summary> 
        /// Gets or sets the tool-tip object that is displayed for this element
        /// in the user interface (UI).
        /// </summary> 
        public object ToolTip
        {
            get { return ToolTipService.GetToolTip(this); }
            set { ToolTipService.SetToolTip(this, value); }
        }

        #endregion ToolTip

        /// <summary> 
        /// Occurs when the low value changes. 
        /// </summary>
        public event RoutedPropertyChangedEventHandler<double> ValueLowChanged;

        /// <summary> 
        /// Occurs when the high value changes. 
        /// </summary>
        public event RoutedPropertyChangedEventHandler<double> ValueHighChanged;


        /// <summary> 
        /// Occurs when the high value changes. 
        /// </summary>
        public event RoutedPropertyChangedEventHandler<ValueChangedEventArgs> ValueChanged;

        /// <summary> 
        /// Initializes a new instance of the LowHighRangeBase class.
        /// </summary>
        protected DoubleRangeBase()
        {
            Minimum = 0;
            Maximum = 1;
            ValueLow = 0;
            ValueHigh = 1;
        }

        /// <summary> 
        /// Called when the Maximum property changes. 
        /// </summary>
        /// <param name="oldMaximum">Old value of the Maximum property.</param> 
        /// <param name="newMaximum">New value of the Maximum property.</param>
        protected virtual void OnMaximumChanged(double oldMaximum, double newMaximum)
        {
        }

        /// <summary> 
        /// Called when the Minimum property changes. 
        /// </summary>
        /// <param name="oldMinimum">Old value of the Minimum property.</param> 
        /// <param name="newMinimum">New value of the Minimum property.</param>
        protected virtual void OnMinimumChanged(double oldMinimum, double newMinimum)
        {
        }

        /// <summary> 
        /// Called when the Low Value property changes. 
        /// </summary>
        /// <param name="oldValue">Old value of the Value property.</param> 
        /// <param name="newValue">New value of the Value property.</param>
        protected virtual void OnValueLowChanged(double oldValue, double newValue)
        {
            RoutedPropertyChangedEventHandler<double> handler = ValueLowChanged;
            if (handler != null)
            {
                handler(this, new RoutedPropertyChangedEventArgs<double>(oldValue, newValue));
            }
        }

        /// <summary> 
        /// Called when the High Value property changes. 
        /// </summary>
        /// <param name="oldValue">Old value of the Value property.</param> 
        /// <param name="newValue">New value of the Value property.</param>
        protected virtual void OnValueHighChanged(double oldValue, double newValue)
        {
            RoutedPropertyChangedEventHandler<double> handler = ValueHighChanged;
            if (handler != null)
            {
                handler(this, new RoutedPropertyChangedEventArgs<double>(oldValue, newValue));
            }
        }

        /// <summary> 
        /// Called when the Value property changes. 
        /// </summary>
        /// <param name="oldValue">Old value of the Value property.</param> 
        /// <param name="newValue">New value of the Value property.</param>
        protected virtual void OnValueChanged(double oldValue, double newValue, ValueChangeType type)
        {
            RoutedPropertyChangedEventHandler<ValueChangedEventArgs> handler = ValueChanged;
            if (handler != null)
            {
                ValueChangedEventArgs oVal = new ValueChangedEventArgs(oldValue, type);
                ValueChangedEventArgs nVal = new ValueChangedEventArgs(newValue, type);
                handler(this, new RoutedPropertyChangedEventArgs<ValueChangedEventArgs>(oVal, nVal));
            }
        }


        /// <summary>
        /// Ensure the Maximum is greater than or equal to the minimum. 
        /// </summary>
        private void CoerceMaximum()
        {
            double minimum = Minimum;
            double maximum = Maximum;
            if (_requestedMax != maximum && _requestedMax >= minimum)
            {
                SetValue(MaximumProperty, _requestedMax);
            }
            else if (maximum < minimum)
            {
                SetValue(MaximumProperty, minimum);
            }
        }



        /// <summary>
        /// Ensure the low and high values fall between the Minimum and Maximum values and correctly set
        /// This function assumes that (Maximum >= Minimum) 
        /// </summary>
        private void CoerceValues()
        {
            // Ensure it's a valid value 
            if (!IsValidDoubleValue(_requestedValueLow) | !IsValidDoubleValue(_requestedValueHigh) | !IsValidDoubleValue(_requestedMax) | !IsValidDoubleValue(_requestedMin))
            {
                throw new ArgumentException("Invalid double value", MinimumProperty.ToString());
            }

            double minimum = Minimum;
            double maximum = Maximum;
            double valueHigh = ValueHigh;
            double valueLow = ValueLow;

            if (valueHigh < minimum)
            {
                SetValue(ValueHighProperty, minimum);
                return;
            }
            if (valueHigh > maximum)
            {
                SetValue(ValueHighProperty, maximum);
                return;
            }

            if (valueLow < minimum)
            {
                SetValue(ValueLowProperty, minimum);
                return;
            }
            if (valueLow > maximum)
            {
                SetValue(ValueLowProperty, maximum);
                return;
            }

            if (_requestedValueHigh < valueLow)
                _requestedValueHigh = valueLow;


            if (_requestedValueHigh > maximum)
                _requestedValueHigh = maximum;

            if (_requestedValueHigh < minimum)
                _requestedValueHigh = minimum;


            if (_requestedValueHigh != valueHigh)
            {
                SetValue(ValueHighProperty, _requestedValueHigh);
                //return;
            }

            if (_requestedValueLow > valueHigh)
                _requestedValueLow = valueHigh;


            if (_requestedValueLow > maximum)
                _requestedValueLow = maximum;

            if (_requestedValueLow < minimum)
                _requestedValueLow = minimum;


            if (_requestedValueLow != valueLow)
            {
                SetValue(ValueLowProperty, _requestedValueLow);
                //return;
            }


        }

        /// <summary>
        /// Check if a value is a value double.
        /// </summary> 
        /// <param name="value">Value.</param>
        /// <returns>true if a valid double; false otherwise.</returns>
        /// <remarks> 
        /// This method is set to private, and is only expected to be 
        /// called from our property changed handlers.
        /// </remarks> 
        private static bool IsValidDoubleValue(object value)
        {
            double number = (double)value;
            return !double.IsNaN(number) && !double.IsInfinity(number);
        }

        /// <summary>
        /// Check if a value is a valid change for the two change properties. 
        /// </summary>
        /// <param name="value">Value.</param>
        /// <returns>true if a valid value; false otherwise.</returns> 
        private static bool IsValidChange(object value)
        {
            return IsValidDoubleValue(value) && (((double)value) >= 0);
        }

        /// <summary> 
        /// Provides a string representation of a LowHighRangeBase object.
        /// </summary>
        /// <returns> 
        /// Returns the string representation of a LowHighRangeBase object.
        /// </returns>
        public override string ToString()
        {
            return string.Format(CultureInfo.InvariantCulture, FormatString, base.ToString(), Minimum, Maximum, ValueLow, ValueHigh);
        }

        /// <summary>
        /// Format string for LowHighRangeBase 
        /// </summary>
        private const string FormatString = "{0} Minimum:{1} Maximum:{2} Low Value:{3} High Value:{4}";
    }

    public class ValueChangedEventArgs : EventArgs
    {
        internal ValueChangedEventArgs(double value, ValueChangeType type)
        {
            Value = value;
            ValueType = type;
        }
        public ValueChangeType ValueType { get; set; }
        public double Value { get; set; }
    }

    public enum ValueChangeType
    {
        High,
        Low
    }
}
